/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	ParameterContainer.cpp
*
*	Comments	-	See ParameterContainer.h
*
**************************************************************************************/

#include "../Include/ParameterContainer.h"
#include "../Include/ParameterBinderScalar.h"
#include "../Include/ParameterBinderMatrixRef.h"
#include "../Include/ParameterBinderMatrixRef.h"
#include "../Include/ParameterBinderMatrixArray.h"
#include "../Include/ParameterBinderShaderResource.h"
#include "../Include/ParameterBinderVector3.h"
#include "../Include/ParameterBinderVector4.h"
#include "../Include/ParameterBinderShaderResource.h"
#include "../Include/ParameterBinderConstantBuffer.h"
#include "../Include/ParameterBinderSamplerState.h"
#include "../Include/ParameterSystem.h"
#include "../Include/MaterialElementInfo.h"
#include "../Include/MaterialElementEntry.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/Tokenizer.h"
#include "../Include/XMLParser.h"
#include "../Include/Engine.h"
#include "../Include/IParameter.h"

namespace Pulse
{
	
	ParameterContainer::ParameterContainer( void )
	{
		 
	}

	ParameterContainer::~ParameterContainer( void )
	{
		ClearParameters();
	}

	IParameterBinder * ParameterContainer::AddParameterBinder( IParameter *pParam )
	{
		if ( !pParam )
			return PSX_NULL;
		
		for ( SIZE_T i = 0; i < m_binders.GetSize(); ++i )
		{
			if ( PSX_StrCmp( m_binders[i]->GetParameter()->GetName(), pParam->GetName() ) == 0 )
			{
				PSX_PushWarning( "Parameter already in the list. Ignoring this binder." );
				return PSX_NULL;
			}
		}
		
		IParameterBinder *pBinder = CreateBinder( pParam );

		if ( pBinder == PSX_NULL )
		{	
			PSX_PushWarning( "Failed to create ParameterBinder for the given param." );
			return PSX_NULL;
		}

		m_binders.PushBack( pBinder );
		return pBinder;
	}

	IParameterBinder * ParameterContainer::AddParameterBinder( EParameter::Type paramType, const CHAR *pName )
	{
		if ( paramType == EParameter::UNKNOWN )
		{
			PSX_PushWarning( "Creating parameter binder with unknown parameter type. Skipping this creation." );
			return PSX_NULL;
		}

		if ( pName == PSX_NULL )
		{
			PSX_PushError( "pName parameter is null." );
			return PSX_NULL;
		}

		ParameterSystem *pParamSys = Engine::GetInstance()->GetRenderer()->GetParameterSystem();

		// Figure out the type to create the parameter to store
		IParameter *pParam = PSX_NULL;
		IParameterBinder *pBinder = PSX_NULL;

		switch ( paramType )
		{
		case EParameter::VECTOR3:
			{
				pParam = pParamSys->GetVector3( pName );
				pBinder = AddParameterBinder( pParam );
			}
			break;
		case EParameter::VECTOR4:
			{
				pParam = pParamSys->GetVector4( pName );
				pBinder = AddParameterBinder( pParam );
			}
			break;
		case EParameter::MATRIX:
			{
				pParam = pParamSys->GetMatrix( pName );
				pBinder = AddParameterBinder( pParam );
			}
			break;
		case EParameter::MATRIX_ARRAY:
			//pParam = pParamSys->GetMatrixArray( entry.m_name.GetBuffer() );
			//pBinder = pParams->AddParameterBinder( pParam );
			PSX_PushError( "Matrix Array not supported yet." );
			break;
		case EParameter::TEXTURE2D:
			{
				pParam = pParamSys->GetShaderResource( pName, EShaderResource::TEXTURE2D );
				pBinder = AddParameterBinder( pParam );
			}
			break;
		case EParameter::TEXTURE3D:
			//break;
		default:
			PSX_PushError( "Not yet supported." );
		}

		return pBinder;
	}

	IParameterBinder * ParameterContainer::GetParameterBinder( UINT index )
	{
		if ( index < m_binders.GetSize() )
			return m_binders[index];
		else
			return PSX_NULL;
	}

	IParameterBinder * ParameterContainer::GetParameterBinder( IParameter *pParam )
	{
		if ( pParam == PSX_NULL )
			return PSX_NULL;

		for ( SIZE_T i = 0; i < m_binders.GetSize(); ++i )
		{
			IParameterBinder *pBinder = m_binders[i];
		
			if ( pBinder->GetType() == pParam->GetType() )
			{
				if ( PSX_StrCmp( pBinder->GetParameter()->GetName(), pParam->GetName() ) == 0 )
				{
					return pBinder;
				}
			}
		}

		return PSX_NULL;
	}

	BOOL ParameterContainer::RemoveParameterBinder( const CHAR *pName )
	{
		if ( pName == PSX_NULL )
		{
			return FALSE;
		}

		for ( SIZE_T i = 0; i < m_binders.GetSize(); ++i )
		{
			IParameterBinder *pBinder = m_binders[i];

			if ( PSX_StrCmp( pBinder->GetParameter()->GetName(), pName ) == 0 )
			{
				m_binders.Remove( i );
				delete pBinder;
				return TRUE;
			}
		}

		return FALSE;
	}

	void ParameterContainer::ClearParameters( void )
	{
		for ( SIZE_T i = 0; i < m_binders.GetSize(); ++i )
		{
			delete m_binders[i];
		}

		m_binders.Clear();
	}

	void ParameterContainer::BindParameters( void )
	{
		for ( SIZE_T i = 0; i < m_binders.GetSize(); ++i )
		{
			m_binders[i]->BindValue();
		}
	}

	EErrorCode::Type ParameterContainer::ParseParameters( XMLElementPtr pElem )
	{
		ParameterSystem *pParamSys = Engine::GetInstance()->GetRenderer()->GetParameterSystem();
		XMLElementPtr pEntry = pElem->GetFirstChild();

		while ( pEntry )
		{
			if ( pEntry->GetName() == PSX_String( "Element" ) )
			{
				MaterialElementEntry entry;
				
				if ( MaterialElementInfo::GetEntryElement( pEntry, &entry ) == EErrorCode::OKAY && 
					entry.m_name.IsEmpty() == FALSE )
				{
					// Figure out the type to create the parameter to store
					IParameterBinder *pBinder = AddParameterBinder( entry.m_type, entry.m_name.GetBuffer() );

					switch ( entry.m_type )
					{
					case EParameter::VECTOR3:
						{
							Vector3 vec;
							UPOS_T tokenMark = 0;
							String token;

							for ( UINT i = 0; i < 3; ++i )
							{
								Tokenizer::NextToken( entry.m_valueString.GetBuffer(),
								&tokenMark, &token );
								
								vec[i] = token.ToFLOAT();
							}
							pBinder->SetAsVector3( &vec );
						}
						break;
					case EParameter::VECTOR4:
						{
							Vector4 vec;
							UPOS_T tokenMark = 0;
							String token;

							for ( UINT i = 0; i < 4; ++i )
							{
								Tokenizer::NextToken( entry.m_valueString.GetBuffer(),
								&tokenMark, &token );
								
								vec[i] = token.ToFLOAT();
							}
							pBinder->SetAsVector4( &vec );
						}
						break;
					case EParameter::MATRIX:
						{
							Matrix4x4 mat;
							UPOS_T tokenMark = 0;
							String token;

							for ( UINT i = 0; i < 4; ++i )
							{
								for ( UINT j = 0; j < 4; ++j )
								{
									Tokenizer::NextToken( entry.m_valueString.GetBuffer(),
									&tokenMark, &token );
								
									mat[i][j] = token.ToFLOAT();
								}
							}
							pBinder->SetAsMatrix( &mat );
						}
						break;
					case EParameter::MATRIX_ARRAY:
						//pParam = pParamSys->GetMatrixArray( entry.m_name.GetBuffer() );
						//pBinder = pParams->AddParameterBinder( pParam );
						PSX_PushError( "Matrix Array not supported yet." );
						break;
					case EParameter::TEXTURE2D:
						{
							GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
							Texture2DDesc desc;
							desc.SetToLoad( entry.m_valueString.GetBuffer() );
							Texture2D *pTexture = pGM->CreateTexture2D( entry.m_valueString.GetBuffer(), &desc );
							
							if ( pTexture )
							{
								pBinder->SetAsShaderResource( pTexture );
								pTexture->Release();
							}
							else
							{
								PSX_PushWarning( "Failed to create texture. Skipping this material parameter." );
							}
						}
						break;
					case EParameter::TEXTURE3D:
					default:
						PSX_PushError( "Not yet supported." );
					}
				}
				else
				{
					PSX_PushWarning( "Invalid material entry parameter. Skipping this entry." );
				}

			}

			pEntry = pEntry->GetNextSibling();
		}

		return EErrorCode::OKAY;
	}

	IParameterBinder * ParameterContainer::CreateBinder( IParameter *pParam )
	{
		switch( pParam->GetType() )
		{
		case EParameter::SCALAR:
			return new ParameterBinderScalar( pParam );
		case EParameter::VECTOR3:
			return new ParameterBinderVector3( pParam );
		case EParameter::VECTOR4:
			return new ParameterBinderVector4( pParam );
		case EParameter::MATRIX:
			return new ParameterBinderMatrixRef( pParam );
		case EParameter::MATRIX_ARRAY:
			return new ParameterBinderMatrixArray( pParam );
		case EParameter::SHADER_RESOURCE:
			{
				// There are different binder types for each shader resource type
				ParameterShaderResource *pShaderRes = (ParameterShaderResource*)pParam;
				EShaderResource::Type shaderResType = pShaderRes->GetShaderResourceType();
				
				if ( shaderResType == EShaderResource::TEXTURE2D || shaderResType == EShaderResource::BUFFER ||
					shaderResType == EShaderResource::STRUCTURED_BUFFER || shaderResType == EShaderResource::UNORDERED_ACCESS )
				{
					return new ParameterBinderShaderResource( pParam );
				}
				else
				{
					PSX_PushError( "Unknown EShaderResource type. Not yet supported." );
				}
				break;
			}
		case EParameter::CONSTANT_BUFFER:
			return new ParameterBinderConstantBuffer( pParam );
		case EParameter::SAMPLER:
			//return new ParameterBinderSa
		case EParameter::UNORDERED_ACCESS:

		case EParameter::ENTITY:
		default:
			PSX_PushError( "Unsupported parameter and parameter binder types." );
		}

		return PSX_NULL;
	}

}